﻿using System;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Text.Json;
using Eamon.MeituanDotnetSDK.api.common;
using Eamon.MeituanDotnetSDK.api.common.request;
using Eamon.MeituanDotnetSDK.api.common.response;
using Eamon.MeituanDotnetSDK.attribute;
using Eamon.MeituanDotnetSDK.@enum;
using Eamon.MeituanDotnetSDK.exception;
using Eamon.MeituanDotnetSDK.executor;
using Eamon.MeituanDotnetSDK.utils;

namespace Eamon.MeituanDotnetSDK.httpclient
{
    public class DefaultOpenAPIClient : IOpenAPIClient
    {
        private readonly OpenAPIHttpClient httpClient;
        private readonly MeituanSdkOptions options;

        public DefaultOpenAPIClient()
        {
            options = new MeituanSdkOptions();
            this.httpClient = new OpenAPIHttpClient();
            //this.httpClient.SetBaseAddress(serverUrl);//用法比较迷惑，算了不用了：https://blog.darkthread.net/blog/httpclient-baseaddress/
            this.httpClient.DeveloperId = options.DeveloperId.ToString();
        }

        public DefaultOpenAPIClient(MeituanSdkOptions options)
        {
            if (options == null)
            {
                throw new MtSdkException("MeituanSdkOptions can not be null");
            }
            this.options = options;
            this.httpClient = new OpenAPIHttpClient();
            //this.httpClient.SetBaseAddress(serverUrl);
            this.httpClient.DeveloperId = options.DeveloperId.ToString();
        }
        public DefaultOpenAPIClient(HttpClient httpClient, MeituanSdkOptions options)
        {
            if (options == null)
            {
                throw new MtSdkException("MeituanSdkOptions can not be null");
            }
            this.options = options;
            this.httpClient = new OpenAPIHttpClient(httpClient);
            //this.httpClient.SetBaseAddress(serverUrl);
            this.httpClient.DeveloperId = options.DeveloperId.ToString();
        }

        public void Dispose()
        {
            if (this.httpClient != null)
            {
                this.httpClient.destroyClient();
            }
        }

        public Task<string> execute(IAPI api)
        {
            Executor executor = null;
            IAPIRequest request = api.getAPIRequest();
            if (request is BaseSignRequest signRequest)
            {
                executor = new SignExecutor(httpClient, true, options.SignKey, options.DeveloperId);
            }
            else if (request is BaseOAuthRequest oAuthRequest)
            {
                executor = new TokenExecutor(httpClient, options.SignKey, options.DeveloperId);
            }
            else
            {
                throw new MtSdkException("Unknown Authenticate Type");
            }

            executor.ExecutionContext context = new executor.ExecutionContext();
            context.setApi(api);
            context.setUrl(api.getHttpUrl());
            return executor.execute(context);
        }

        public async Task<T> InvokeAsync<T>(IAPI api)
        {
            string executeResult = await execute(api);
            Console.WriteLine(executeResult);
            return JsonUtil.JsonToObject<T>(executeResult, typeof(T));
        }

        public Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request)
        {
            return InvokeApiAsync(request, null, CancellationToken.None);
        }

        public Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request, string appAuthToken)
        {
            return InvokeApiAsync(request, appAuthToken, CancellationToken.None);
        }

        public Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request, string appAuthToken, ApiMetaInfo apiMetaInfo)
        {
            return InvokeApiAsync(request, appAuthToken, apiMetaInfo, CancellationToken.None);
        }

        public Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request, CancellationToken cancellationToken)
        {
            return InvokeApiAsync(request, null, cancellationToken);
        }

        public async Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request, string appAuthToken, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new MtSdkException("request参数不能为NULL");
            }

            ApiMetaInfo apiMetaInfo = AttributeUtils.GetApiMeta(request);
            return await InvokeApiAsync(request, appAuthToken, apiMetaInfo, cancellationToken);
        }

        public async Task<MeituanResponse<T>> InvokeApiAsync<T>(IMeituanRequest<T> request, string appAuthToken, ApiMetaInfo apiMetaInfo, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new MtSdkException("request参数不能为NULL");
            }
            if (apiMetaInfo == null)
            {
                throw new MtSdkException("apiMetaInfo参数不能为NULL");
            }

            string url = CommonConstants.SERVER_URL_PROD + apiMetaInfo.Path; 
            string bizParams = request.SerializeToJson();
            var queryMap = BuildQueryContentMap(bizParams, apiMetaInfo.BusinessId, appAuthToken);

            // Verify parameters
            VerifyParams(request, apiMetaInfo, appAuthToken);

            string responseJson = await httpClient.GetResponse(url, HttpMethodEnum.POST, queryMap, cancellationToken);

            if (string.IsNullOrEmpty(responseJson))
            {
                throw new MtSdkException("请求返回结果为空");
            }

            try
            {
                return request.DeserializeResponse(responseJson);
            }
            catch (JsonException e)
            {
                throw new MtSdkException("解析请求返回值为Response失败", e);
            }
        }

        public string GetGeneralAuthUrl(GeneralAuthUrlRequest request)
        {
            var queryMap = new Hashtable
                {
                    { CommonConstants.DEVELOPER_ID, this.options.DeveloperId.ToString() },
                    { CommonConstants.BUSINESS_ID, request.BusinessId },
                    { CommonConstants.TIMESTAMP, TimeUtil.formatTime(DateTimeOffset.Now) },
                    { CommonConstants.CHARSET, CommonConstants.CHARSET_UTF8 }            
                };
            if(!string.IsNullOrEmpty(request.State))
            {
                queryMap.Add("state", request.State);
            }
            if (!string.IsNullOrEmpty(request.Scope))
            {
                queryMap.Add("scope", request.Scope);
            }
            if (request.QrMode != null)
            {
                queryMap.Add("qrMode", request.QrMode);
            }
            string paramSign = EncryptUtil.getSign(options.SignKey, queryMap);
            queryMap.Add(CommonConstants.SIGN, paramSign);
            //将queryMap拼装成url
            string baseUrl = "https://open-erp.meituan.com/general/auth?";
            string queryStr = string.Join("&", queryMap.Keys.Cast<string>().Select(key => $"{key}={queryMap[key]}"));
            return baseUrl + queryStr;
        }

        private Hashtable BuildQueryContentMap(string bizParams, int businessId, string appAuthToken)
        {
            var queryMap = new Hashtable
                {
                    { CommonConstants.BIZ_FIELD, bizParams },
                    { CommonConstants.TIMESTAMP, TimeUtil.formatTime(DateTimeOffset.Now) },
                    { CommonConstants.BUSINESS_ID, businessId.ToString() },
                    { CommonConstants.DEVELOPER_ID, this.options.DeveloperId.ToString() },
                    { CommonConstants.CHARSET, CommonConstants.CHARSET_UTF8 },
                    { CommonConstants.VERSION, this.options.Version.ToString() }
                };

            if (!string.IsNullOrEmpty(appAuthToken))
            {
                queryMap.Add(CommonConstants.APP_AUTH_TOKEN, appAuthToken);
            }
            string paramSign = EncryptUtil.getSign(options.SignKey, queryMap);
            queryMap.Add(CommonConstants.SIGN, paramSign);

            return queryMap;
        }

        private void VerifyParams<T>(IMeituanRequest<T> request, ApiMetaInfo apiMetaInfo, string appAuthToken)
        {
            if (apiMetaInfo.NeedAuth && string.IsNullOrEmpty(appAuthToken))
            {
                throw new MtSdkException("此接口需要鉴权，缺少appAuthToken参数");
            }
            var validationContext = new ValidationContext(request, serviceProvider: null, items: null);
            var validationResults = new List<ValidationResult>();

            bool isValid = Validator.TryValidateObject(request, validationContext, validationResults, true);

            if (!isValid)
            {
                foreach (var validationResult in validationResults)
                {
                    throw new ValidationException(validationResult.ErrorMessage);
                }
            }
        }
    }
}
